package cn.easyshipping.cloud.common.log;

import brave.propagation.TraceContext;
import cn.easyshipping.cloud.business.dto.ResponseResult;
import cn.easyshipping.cloud.common.constant.ErrorCode;
import cn.easyshipping.cloud.common.exception.BusinessException;
import cn.easyshipping.cloud.common.util.ResponseUtil;
import com.alibaba.fastjson.JSON;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.ResponseFacade;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Aspect
@Component
public class OperationLog {

    private static Logger log = LoggerFactory.getLogger(OperationLog.class.getSimpleName());

    @Autowired
    private ResponseUtil responseUtil;

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    private void pointcutGet(){}

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    private void pointcutPost(){}

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
    private void pointcutPut(){}

    @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    private void pointcutDelete(){}

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    private void pointcutAll(){}

    @Around("pointcutGet() || pointcutPost() || pointcutPut() || pointcutDelete()|| pointcutAll()")
    public Object round(ProceedingJoinPoint joinPoint) throws Throwable{
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        HttpServletResponse response=attributes.getResponse();
        Object[] argsValue=joinPoint.getArgs();
        String requestUrl = request.getRequestURL().toString();

        String traceId = "";
        Object traceContext = request.getAttribute("brave.propagation.TraceContext");
        //Zipkin链路追踪用的TraceId
        if (traceContext instanceof TraceContext) {
            traceId = ((TraceContext) traceContext).traceIdString();
        }

        ApiLogInfo apiLogInfo = new ApiLogInfo();
        apiLogInfo.setLogID(UUID.randomUUID().toString());
        apiLogInfo.setTraceID(traceId);
        apiLogInfo.setApiName(request.getServletPath());
        apiLogInfo.setRequestMethod(request.getMethod());
        apiLogInfo.setHeadAuth(request.getHeader("x-ryd-authorization"));
        String app="";
        if (request.getQueryString()!=null){
            app="?"+request.getQueryString();
        }
        apiLogInfo.setRequestUrl(requestUrl + app);
        Object result=null;
        Date now = new Date();
        try {
            Map<String, Object> params = getParams(joinPoint, argsValue);

            apiLogInfo.setPostData(JSON.toJSONString(params));
            apiLogInfo.setRequestTime(now);
            apiLogInfo.setUnixTS(now.getTime());

            result = joinPoint.proceed(argsValue);

            String json=null;
            if (!(result instanceof String)){
                json = JSON.toJSONString(result);
            }
            String ResData=json==null?result.toString():json;
            apiLogInfo.setResponseData(ResData);
            apiLogInfo.setResponseTime(now);

            log.info(JSON.toJSONString(apiLogInfo));

        } catch (Exception e) {

            String jsonRepo = "";

            apiLogInfo.setPostData(Arrays.toString(joinPoint.getArgs()));
            apiLogInfo.setRequestTime(now);

            if (e instanceof BusinessException) {
                jsonRepo = JSON.toJSONString(responseUtil.failedResult(String.valueOf(((BusinessException) e).getMessageCode()), ((BusinessException) e).getDetailMessage()));
            } else {
                jsonRepo = JSON.toJSONString(responseUtil.failedResult(ErrorCode.serverError));
            }

            apiLogInfo.setResponseData(jsonRepo);
            apiLogInfo.setException(getStackMsg(e));

            log.error(jsonRepo);

            response.getOutputStream().write(jsonRepo.getBytes());
            response.setContentType("json");

            e.printStackTrace();
        }

        return result;
    }

    private Map<String, Object> getParams(ProceedingJoinPoint joinPoint, Object[] argsValue) {
        Map<String, Object> params = new HashMap<>();
        MethodSignature joinPointMethod = (MethodSignature) joinPoint.getSignature();
        String[] argsName = joinPointMethod.getParameterNames();
        for (int i = 0; i < argsValue.length; i++) {
            if (isJsonObject(argsValue[i])) {
                params.put(argsName[i], argsValue[i]);
            }
        }

        return params;
    }

    private boolean isJsonObject(Object argsValue){
        if (argsValue.getClass() != RequestFacade.class && argsValue.getClass() != ResponseFacade.class && !argsValue.getClass().toString().contains("UrlRewriteWrappedResponse") && !argsValue.getClass().toString().contains("ApplicationHttpRequest")
                && !argsValue.getClass().toString().contains("StandardMultipartHttpServletRequest"))
            return true;
        else
            return false;
    }

    private String getStackMsg(Throwable e) {
        StringBuffer sb = new StringBuffer();
        StackTraceElement[] stackArray = e.getStackTrace();
        for (int i = 0; i < stackArray.length&&i<15; i++) {
            StackTraceElement element = stackArray[i];
            sb.append(element.toString() + "\n");
        }
        return sb.toString();
    }

}
